package com.hjk.common.utils.security;


import com.hjk.common.model.Payload;

import com.hjk.common.utils.date.MyDateTimeUtils;
import com.hjk.common.utils.string.JsonUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

/**
 * @author hjk
 **/

@SuppressWarnings("unchecked")
public class JwtUtils {
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * token里面的负载key
     */
    private static final String JWT_PAYLOAD_USER_KEY = "data";

    private static final Decoder<String, byte[]> STRING_DECODER = Decoders.BASE64URL;
    /**
     * 密钥长度
     */
    private static final int DEFAULT_KEY_SIZE = 2048;
    /**
     * 私钥
     */
    private static final String PRIVATE_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCKXRBIdcRP+AWFaE2L/6t3fA1YQ88SCIlkheR9QO1CyJmK8FUR01l2wwOQlMGTJy0N9Xs/vzmojjQ1qx5H/00bZwG9EK9mwh36KK9khdZjD1/IMBVb3/E6S0N2VQ8xVmhJVRP3kAoWq5SXkfsRRBexgoBH8oy6DHn/6C8Enb5oanYzcyCTVuSgB+vYlA0bdhTMjk0KWnBkEcRpAh2RJZKb7kf0O8YdtjzQhSPfxo5ey6joSC/XfpgBXsFm3Z9ZMAkmt7db2/6CDvIdblDItXWIh+m8ESF7Ncl61N9BuHfTabfvUiajZ5FomQMYowimcnprh3dE0Cd4irHlsT16AbhzAgMBAAECggEAOE5zB+nU/+4g4Q93L1fWQYOcMqU1qVMMBnpuZ4J6i0jzIxV2qRBnQetOkhHXTLkOnrWdeG2rWyqzmHuQkSAWEuayUGxv1NN+O4U/CxtkCWACTIc0yfSySFTEmW04B43wVpdmzsIxONSOXbgsgfRB6B0Bc0YDetsE8t1b9Pb0u6yrFdncdcFr5/LmZt92hk+vOkEiVxqmC/CeewFBkOUc8Xc4bVOQ7aBWbx++WXlsw3l5AwcyibZ2Fg73q8iIDFjbhMuYwnYfeGKIrV51G4WgbLZ3DEr8XgeqZv5Z1qtbi5FGCzSTQmGkSyEsLNqU1s0dRVdb0PU9CAMQ42Qi6apdYQKBgQDm3nOMDcMOcTjXTIe1naGXm0oHJZyr4PFcNrQf0nKQ/Ar71mazng8p/TQepfAiLs43cx/CU3Ym4IQdAXHF88pvinT5lhptU4sufPz/1z+5psHDX68xSTwsORa3k6L3mpQt9qNRjfr/P8ecmbU2LAtbLhjaxSLzuVLA2G3z0UyfmwKBgQCZbMtXK4DkUX3BgBvNNp/G35i8m/QktKtRGvtqtYQqbt/1Pf0FICu6ds0yD1ErdrYyPDJWgh9xS87lVZ6lzb8P6Mp0EoTTmbu3CLaOrtFnKN+WxlV0F5jkgxCjnCRWq2XGNXnN2zBrGqe/jaCJHF+Yi5RXutpHOHNPpze/PwgUCQKBgEWRGnhFXVVowJv/r5UGXr70SCZHKyKPsar7wOMl4ufVSun//Qp6wk8AwUfK8MUcHX+P7S39QEkOtkCXQtVHVOSvb/gVRvlSh0oG16WwEr+6hAB/QKc1TjB1M2c10ZGSvrvq6eOVSODJMzj5CCW+t91N0qd5Z8LRQSJjFR63HsOpAoGAFbQtQ8SUtDfO+WudAzfB/Iku+yd4rySbw5p0jJDnBHWw2umM/Iejh/o8ZuOB0Qs3FP4tBM3dktJtj0XAbqYfbCm7UhY4khKCWo8x/BdA6j3++z5NYmHgMBtWZD63aUdP220/MN7cB5q6tj9bwWmxIExGjO6S6FZVAaSfHz/UbbkCgYAnAmpcToKAmwJmG2/hkGZMO2W1jLP0ZamXgKi9to6E9kyMtI4bDbB+/4vYoadZhOwJneK2sRd3NMi1hsmjfADahsF25bQ5WD4tZccZ+exM+kgEq+Q2mttq9XxdtbYQBREy9w4fD7lTtZq9IsK6m2pXeoL7nVt9XHcv56G0pAeoIQ==";
    /**
     * 公钥
     */
    private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAil0QSHXET/gFhWhNi/+rd3wNWEPPEgiJZIXkfUDtQsiZivBVEdNZdsMDkJTBkyctDfV7P785qI40NaseR/9NG2cBvRCvZsId+iivZIXWYw9fyDAVW9/xOktDdlUPMVZoSVUT95AKFquUl5H7EUQXsYKAR/KMugx5/+gvBJ2+aGp2M3Mgk1bkoAfr2JQNG3YUzI5NClpwZBHEaQIdkSWSm+5H9DvGHbY80IUj38aOXsuo6Egv136YAV7BZt2fWTAJJre3W9v+gg7yHW5QyLV1iIfpvBEhezXJetTfQbh302m371Imo2eRaJkDGKMIpnJ6a4d3RNAneIqx5bE9egG4cwIDAQAB";

    /**
     * 读取公钥
     *
     * @return 公钥对象
     */
    public static PublicKey getPublicKey() {
        try {
            byte[] bytes = PUBLIC_KEY.getBytes();
            return getPublicKey(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 读取密钥
     *
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey() {
        try {
            byte[] bytes = PRIVATE_KEY.getBytes();
            return getPrivateKey(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从文件中读取公钥
     *
     * @param filePath 公钥保存路径，相对于classpath
     * @return 公钥对象
     */
    public static PublicKey getPublicKey(String filePath) {
        try {
            byte[] bytes = readFile(filePath);
            return getPublicKey(bytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 从文件中读取密钥
     *
     * @param filePath 私钥保存路径，相对于classpath
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filePath) {
        try {
            byte[] bytes = readFile(filePath);
            return getPrivateKey(bytes);

        } catch (Exception e) {
            e.printStackTrace();

        }
        return null;
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     */
    private static PublicKey getPublicKey(byte[] bytes) {
        try {
            bytes = Base64.getDecoder().decode(bytes);
            X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePublic(spec);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     */
    private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        bytes = Base64.getDecoder().decode(bytes);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文，生成rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }


    /**
     * 私钥加密token
     *
     * @param data       载荷中的数据
     * @param expire     过期时间，单位分钟
     * @return JWT
     */
    public static String generateTokenExpireInMinutes(Object data, long expire) {
        Date date = MyDateTimeUtils.toDate(LocalDateTime.now().plusMinutes(expire));
        String token = Jwts.builder()
                .claim(JWT_PAYLOAD_USER_KEY, data instanceof String ? data :  JsonUtils.toJsonString(data)) //存入信息
                .setId(createJTI())  //随机生成id
                .setExpiration(date) //设置token过期时间,当前时间+expire
                .signWith(getPrivateKey(), SignatureAlgorithm.RS256) //签名的加密算法
                .compact();

        System.out.println(data.toString()+" 生成token  [" + token+"]"+"过期时间 : " + date);
        return token;
    }

    /**
     * 私钥加密token
     *
     * @param data       载荷中的数据
     * @param expire     过期时间，单位小时
     * @return JWT
     */
    public static String generateTokenExpireInHour(Object data, long expire) {
        return generateTokenExpireInMinutes(data,expire * 60);
    }

    /**
     * 私钥加密token
     *
     * @param data       载荷中的数据
     * @param expire     过期时间，单位天
     * @return JWT
     */
    public static String generateTokenExpireInDay(Object data, long expire) {
        return generateTokenExpireInHour(data,expire * 24);
    }




    /**
     * 公钥解析token
     *
     * @param token     用户请求中的token
     * @param publicKey 公钥
     * @return Jws<Claims>
     */
    private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
        return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
    }

    /**
     * 生成token的ID
     *
     * @return 随机id
     */
    private static String createJTI() {
        return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
    }

    /**
     * 获取token中的信息
     *
     * @param token     用户请求中的令牌
     * @param publicKey 公钥
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> data) {
        Jws<Claims> claimsJws = parserToken(token, publicKey);
        Claims body = claimsJws.getBody();
        Payload<T> claims = new Payload<>();
        claims.setId(body.getId());
        //获得token第二部分信息 用户信息
        claims.setData(JsonUtils.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(), data));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取token中的载荷信息
     *
     * @param token     用户请求中的令牌
     * @return 用户信息
     */
    public static  Payload getInfoFromToken(String token) {
        Claims body = parserToken(token, getPublicKey()).getBody();
        Payload claims = new Payload();
        claims.setId(body.getId());
        claims.setData(body.get(JWT_PAYLOAD_USER_KEY));
        claims.setExpiration(body.getExpiration());
        return claims;
    }

    /**
     * 获取token中的载荷信息
     *
     * @param token 用户请求中的令牌
     * @return 用户信息
     */
    public static <T> Payload<T> getInfoFromToken(String token, Class<T> data) {
        try {
            Claims body = parserToken(token, getPublicKey()).getBody();
            Payload<T> claims = new Payload<>();
            claims.setId(body.getId());
            claims.setData(JsonUtils.toObject(body.get(JWT_PAYLOAD_USER_KEY).toString(), data));
            claims.setExpiration(body.getExpiration());
            return claims;

        } catch (Exception e) {
            e.printStackTrace();

        }
        return null;
    }


    public static void main(String[] args) {
        String token = generateTokenExpireInMinutes("800162269", 1);

        System.out.println(token);

        System.out.println(getInfoFromToken(token));
    }


}
